---
title: Create a modal
description: In this tutorial, learn how to create a modal from React Native to select an image.
---

import { BookOpen02Icon } from '@expo/styleguide-icons/outline/BookOpen02Icon';

import { BoxLink } from '~/ui/components/BoxLink';
import { ContentSpotlight } from '~/ui/components/ContentSpotlight';
import { SnackInline } from '~/ui/components/Snippet';
import { Step } from '~/ui/components/Step';

React Native provides a [`<Modal>` component](https://reactnative.dev/docs/modal) that presents content above the rest of your app. In general, modals are used to draw a user's attention toward critical information or guide them to take action. For example, in the [second chapter](/tutorial/build-a-screen/#step-7-enhance-the-reusable-button-component),
we used `alert()` to display a placeholder when a button is pressed. That's how a modal component displays an overlay.

In this chapter, we'll create a modal that shows an emoji picker list.

<Step label="1">

## Declare a state variable to show buttons

Before implementing the modal, we are going to add three new buttons. These buttons will only be visible when the user picks an image from the media library
or decides to use the placeholder image. One of these buttons will trigger the emoji picker modal.

Declare a state variable called `showAppOptions` in **App.js**. We'll use this variable to show or hide buttons that open the modal alongside a few other options.

This variable is a boolean. When the app screen loads, we'll set it to `false` so that the options are not shown before picking an image.

{/* prettier-ignore */}
```jsx App.js
export default function App() {
  /* @info Create a state variable inside the App component. */const [showAppOptions, setShowAppOptions] = useState(false); /* @end */
  // ...rest of the code remains same
}
```

The value of this variable will be set to `true` when the user picks an image from the media library or decides to use the placeholder image.

Next, modify the `pickImageAsync()` function to set the value of `showAppOptions` to `true` after the user picks an image.

{/* prettier-ignore */}
```jsx App.js
const pickImageAsync = async () => {
  // ...rest of the code remains same

  if (!result.canceled) {
    setSelectedImage(result.assets[0].uri);
    /* @info After selecting the image, set the App options to true. */ setShowAppOptions(true); /* @end */

  } else {
    // ...rest of the code remains same
  }
};
```

Then, update the button with no theme by adding an `onPress` prop with the following value:

{/* prettier-ignore */}
```jsx App.js
<Button label="Use this photo" /* @info Update the onPress prop.*/ onPress={() => setShowAppOptions(true)} /* @end */ />
```

Now, we can remove the `alert` on the `<Button>` component and update the `onPress` prop when rendering the second button in **Button.js**:

{/* prettier-ignore */}
```jsx Button.js
<Pressable style={styles.button} /* @info Replace the alert() with the onPress.*/ onPress={onPress}/* @end */ >
```

Next, update **App.js** to conditionally render the `<Button>` component based on the value of `showAppOptions`. Also, move the buttons in the conditional operator block.

{/* prettier-ignore */}
```jsx App.js
export default function App() {
  // ...
  return (
    <View style={styles.container}>
      {/* ...rest of the code remains same */}
      /* @info Based on the value of showAppOptions, the buttons will be displayed. Also, move the existing buttons in the conditional operator block. */
      {showAppOptions ? (
        <View />
      /* @end */
      /* @info */) : ( /* @end */
        <View style={styles.footerContainer}>
          <Button theme="primary" label="Choose a photo" onPress={pickImageAsync} />
          <Button label="Use this photo" onPress={() => setShowAppOptions(true)} />
        </View>
      /* @info */)}/* @end */
      <StatusBar style="auto" />
    </View>
  );
}
```

For now, when the value of `showAppOptions` is `true`, let's render an empty `<View>` component. We'll address this state in the next step.

</Step>

<Step label="2">

## Add buttons

Let's break down the layout of the option buttons we will implement in this chapter. The design looks like this:

<ContentSpotlight
  alt="Break down of the layout of the buttons row."
  src="/static/images/tutorial/buttons-layout.jpg"
  className="max-w-[480px]"
/>

It contains a parent `<View>` with three buttons aligned in a row. The button in the middle with the plus icon (+) will open the modal and is styled differently than the other two buttons.

Inside the **components** directory, create a new file called **CircleButton.js** with the following code:

```jsx CircleButton.js
import { View, Pressable, StyleSheet } from 'react-native';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';

export default function CircleButton({ onPress }) {
  return (
    <View style={styles.circleButtonContainer}>
      <Pressable style={styles.circleButton} onPress={onPress}>
        <MaterialIcons name="add" size={38} color="#25292e" />
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  circleButtonContainer: {
    width: 84,
    height: 84,
    marginHorizontal: 60,
    borderWidth: 4,
    borderColor: '#ffd33d',
    borderRadius: 42,
    padding: 3,
  },
  circleButton: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 42,
    backgroundColor: '#fff',
  },
});
```

To render the plus icon, this button uses the `<MaterialIcons>` icon set from the `@expo/vector-icons` library.

The other two buttons also use `<MaterialIcons>` to display vertically aligned text labels and icons. Next, create a file named **IconButton.js** inside the **components** directory. This component accepts three props:

- `icon`: the name that corresponds to the icon in the `MaterialIcons` library.
- `label`: the text label displayed on the button.
- `onPress`: the function called when the button is pressed.

```jsx IconButton.js
import { Pressable, StyleSheet, Text } from 'react-native';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';

export default function IconButton({ icon, label, onPress }) {
  return (
    <Pressable style={styles.iconButton} onPress={onPress}>
      <MaterialIcons name={icon} size={24} color="#fff" />
      <Text style={styles.iconButtonLabel}>{label}</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  iconButton: {
    justifyContent: 'center',
    alignItems: 'center',
  },
  iconButtonLabel: {
    color: '#fff',
    marginTop: 12,
  },
});
```

Import these buttons into **App.js** and replace the empty `<View>` component from the previous step to display them. Let's also create the `onPress` functions for these buttons to add the functionality later.

<SnackInline
label="Add Button options"
templateId="tutorial/03-button-options/App"
dependencies={['expo-image-picker', '@expo/vector-icons/FontAwesome', '@expo/vector-icons', 'expo-status-bar', '@expo/vector-icons/MaterialIcons']}
files={{
  'assets/images/background-image.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/503001f14bb7b8fe48a4e318ad07e910',
  'components/ImageViewer.js': 'tutorial/02-image-picker/ImageViewer.js',
  'components/Button.js': 'tutorial/03-button-options/Button.js',
  'components/CircleButton.js': 'tutorial/03-button-options/CircleButton.js',
  'components/IconButton.js': 'tutorial/03-button-options/IconButton.js',
}}>

{/* prettier-ignore */}
```jsx
// ... rest of the import statements
import CircleButton from './components/CircleButton';
import IconButton from './components/IconButton';

export default function App() {
  // ...rest of the code remains same
  /* @info Add placeholder functions that we will add logic for in the next sections. */
  const onReset = () => {
    setShowAppOptions(false);
  };

  const onAddSticker = () => {
    // we will implement this later
  };

  const onSaveImageAsync = async () => {
    // we will implement this later
  };
  /* @end */

  return (
    <View style={styles.container}>
      {/* ...rest of the code remains same */}
      {showAppOptions ? (
        /* @info Replace empty View component with this snippet to display App option buttons. */
        <View style={styles.optionsContainer}>
          <View style={styles.optionsRow}>
            <IconButton icon="refresh" label="Reset" onPress={onReset} />
            <CircleButton onPress={onAddSticker} />
            <IconButton icon="save-alt" label="Save" onPress={onSaveImageAsync} />
          </View>
        </View>
        /* @end */
      ) : (
        // ...rest of the code remains same
      )}
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  // ...previous styles remain unchanged
  /* @info Add styles for the new View components. */
  optionsContainer: {
    position: 'absolute',
    bottom: 80,
  },
  optionsRow: {
    alignItems: 'center',
    flexDirection: 'row',
  },
  /* @end */
})
```

</SnackInline>

In the above snippet, the `onReset()` function is called when the user presses the reset button. When this button is pressed, we'll show the image picker button again.

Let's take a look at our app on Android, iOS and the web:

<ContentSpotlight
  alt="Button options displayed after a image is selected."
  src="/static/images/tutorial/button-options.jpg"
  className="max-w-[720px]"
/>

</Step>

<Step label="3">

## Create an emoji picker modal

The modal allows the user to choose an emoji from a list of available emoji. Create an **EmojiPicker.js** file inside the **components** directory. This component accepts three props:

- `isVisible`: a boolean that determines whether the modal is visible or not.
- `onClose`: a function that closes the modal.
- `children`: used later to display a list of emoji.

{/* prettier-ignore */}
```jsx EmojiPicker.js|collapseHeight=430
import { Modal, View, Text, Pressable, StyleSheet } from 'react-native';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';

export default function EmojiPicker({ isVisible, children, onClose }) {
  return (
    <Modal animationType="slide" transparent={true} visible={isVisible}>
      <View style={styles.modalContent}>
        <View style={styles.titleContainer}>
          <Text style={styles.title}>Choose a sticker</Text>
          <Pressable onPress={onClose}>
            <MaterialIcons name="close" color="#fff" size={22} />
          </Pressable>
        </View>
        {children}
      </View>
    </Modal>
  );
}
```

Let's learn what the above code does.

- The `<Modal>` component displays a title and a close button.
- Its `visible` prop takes the value of `isVisible` and controls if the modal is open or closed.
- Its `transparent` prop is a boolean value that determines whether the modal fills the entire view.
- Its `animationType` prop determines how it enters and leaves the screen. In this case, it is sliding from the bottom of the screen.
- Lastly, the `<EmojiPicker>` `onClose` prop is called when the user presses the close `<Pressable>`.

The next step is to add the corresponding styles for the `<EmojiPicker>` component:

{/* prettier-ignore */}
```jsx EmojiPicker.js
const styles = StyleSheet.create({
  modalContent: {
    height: '25%',
    width: '100%',
    backgroundColor: '#25292e',
    borderTopRightRadius: 18,
    borderTopLeftRadius: 18,
    position: 'absolute',
    bottom: 0,
  },
  titleContainer: {
    height: '16%',
    backgroundColor: '#464C55',
    borderTopRightRadius: 10,
    borderTopLeftRadius: 10,
    paddingHorizontal: 20,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  title: {
    color: '#fff',
    fontSize: 16,
  },
});
```

Now, let's modify the **App.js** to:

- Import the `<EmojiPicker>` component.
- Then, create an `isModalVisible` state variable with the `useState` hook. It has a default value of `false` to ensure that the modal is hidden until the user presses the button to open it.
- Replace the comment in the `onAddSticker()` function to update the `isModalVisible` variable to `true` when the user presses the button. This will open the emoji picker.
- Create a`onModalClose()` function to update the `isModalVisible` state variable.
- Place the `<EmojiPicker>` component at the bottom of the `<App>` component, above the `<StatusBar>` component.

<SnackInline
label="Create a modal"
templateId="tutorial/04-modal/App"
dependencies={['expo-image-picker', '@expo/vector-icons/FontAwesome', '@expo/vector-icons', 'expo-status-bar', '@expo/vector-icons/MaterialIcons']}
files={{
  'assets/images/background-image.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/503001f14bb7b8fe48a4e318ad07e910',
  'components/ImageViewer.js': 'tutorial/02-image-picker/ImageViewer.js',
  'components/Button.js': 'tutorial/03-button-options/Button.js',
  'components/CircleButton.js': 'tutorial/03-button-options/CircleButton.js',
  'components/IconButton.js': 'tutorial/03-button-options/IconButton.js',
  'components/EmojiPicker.js': 'tutorial/04-modal/EmojiPicker.js',
}}>

{/* prettier-ignore */}
```jsx
// ...rest of the import statements remain same
/* @info import the EmojiPicker component.*/ import EmojiPicker from "./components/EmojiPicker"; /* @end */

export default function App() {
  /* @info Create a state variable.*/const [isModalVisible, setIsModalVisible] = useState(false);  /* @end */
  // ...rest of the code remains same

  /* @info Update functions to control the modal's visibility.*/
  const onAddSticker = () => {
    setIsModalVisible(true);
  };

  const onModalClose = () => {
    setIsModalVisible(false);
  };
  /* @end */

  return (
    <View style={styles.container}>
      {/* ...rest of the code remains same */}
      /* @info Render the EmojiPicker component at the bottom of the App component, just before the StatusBar. */
      <EmojiPicker isVisible={isModalVisible} onClose={onModalClose}>
        {/* A list of emoji component will go here */}
      </EmojiPicker>
      /* @end */
      <StatusBar style="auto" />
    </View>
  );
}
```

</SnackInline>

Here is the result after this step:

<ContentSpotlight
  alt="A modal working on all platforms"
  src="/static/images/tutorial/modal-creation.jpg"
  className="max-w-[720px]"

/>

</Step>

<Step label="4">

## Display a list of emoji

Let's implement a horizontal list of emoji in the modal's content. We'll use the [`<FlatList>`](https://reactnative.dev/docs/flatlist) component from React Native for it.

Create a file named **EmojiList.js** file in the **components** directory and add the following code:

{/* prettier-ignore */}
```jsx EmojiList.js
import { useState } from 'react';
import { StyleSheet, FlatList, Image, Platform, Pressable } from 'react-native';

export default function EmojiList({ onSelect, onCloseModal }) {
  const [emoji] = useState([
    require('../assets/images/emoji1.png'),
    require('../assets/images/emoji2.png'),
    require('../assets/images/emoji3.png'),
    require('../assets/images/emoji4.png'),
    require('../assets/images/emoji5.png'),
    require('../assets/images/emoji6.png'),
  ]);

  return (
    <FlatList
      horizontal
      showsHorizontalScrollIndicator={Platform.OS === 'web'}
      data={emoji}
      contentContainerStyle={styles.listContainer}
      renderItem={({ item, index }) => (
        <Pressable
          onPress={() => {
            onSelect(item);
            onCloseModal();
          }}>
          <Image source={item} key={index} style={styles.image} />
        </Pressable>
      )}
    />
  );
}

const styles = StyleSheet.create({
  listContainer: {
    borderTopRightRadius: 10,
    borderTopLeftRadius: 10,
    paddingHorizontal: 20,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  image: {
    width: 100,
    height: 100,
    marginRight: 20,
  },
});
```

The `<FlatList>` component above renders all the emoji images using a `<Image>` component wrapped with a `<Pressable>` component.
Later, we will improve it so that the user can tap an emoji on the screen to make it appear as a sticker on the image.

The `<FlatList>` component takes an array of items, which in the above snippet is provided by the `emoji` array variable as the value of the `data` prop.
Then, the `renderItem` prop takes the item from the `data` and returns the item in the list. Finally, we add `<Image>` and the `<Pressable>` components to display this item.

The `horizontal` prop renders the list horizontally instead of vertically. The `showsHorizontalScrollIndicator` checks the platform using `Platform` module from React Native and displays the horizontal scroll bar only on the web.

Now, modify the `App` component. Import the `<EmojiList>` component and replace the comments where the `<EmojiPicker>` component is used with the following code snippet:

{/* prettier-ignore */}
```jsx App.js|collapseHeight=440
//...rest of the import statements remain same
/* @info Import the EmojiList component.*/ import EmojiList from './components/EmojiList';/* @end */

// Inside App component to select the emoji from the list

export default function App() {
  /* @info Define a state variable. */const [pickedEmoji, setPickedEmoji] = useState(null);/* @end */
  // ...rest of the code remain same

  return (
    <View style={styles.container}>
      {/* rest of the code remains unchanged */}
      <EmojiPicker isVisible={isModalVisible} onClose={onModalClose}>
        /* @info Render the EmojiList component inside the EmojiPicker component. */
        <EmojiList onSelect={setPickedEmoji} onCloseModal={onModalClose} />
        /* @end */
      </EmojiPicker>
      <StatusBar style="auto" />
    </View>
  );
}
```

The `onSelect` prop on the `<EmojiList>` component selects the emoji and the `onCloseModal` prop closes the modal after emoji is selected.

Let's take a look at our app on Android, iOS and the web:

<ContentSpotlight file="tutorial/emoji-picker.mp4" />

</Step>

<Step label="5">

## Display the selected emoji

Now we'll put the emoji sticker on the image.

Start by creating a new file in the **components** directory and call it **EmojiSticker.js**. Then, add the following code:

```jsx EmojiSticker.js
import { View, Image } from 'react-native';

export default function EmojiSticker({ imageSize, stickerSource }) {
  return (
    <View style={{ top: -350 }}>
      <Image
        source={stickerSource}
        resizeMode="contain"
        style={{ width: imageSize, height: imageSize }}
      />
    </View>
  );
}
```

This component receives two props:

- `imageSize`: a value defined inside the `<App>` component. We will use this value in the next chapter to scale the image's size when tapped.
- `stickerSource`: the source of the selected emoji image.

We'll import this component in the **App.js** file and update the `<App>` component to display the emoji sticker on the image conditionally. We'll do this by checking if the `pickedEmoji` state is not `null`.

<SnackInline
label="Display selected emoji sticker"
templateId="tutorial/05-emoji-list/App"
dependencies={['expo-image-picker', '@expo/vector-icons/FontAwesome', '@expo/vector-icons', 'expo-status-bar', '@expo/vector-icons/MaterialIcons']}
files={{
  'assets/images/background-image.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/503001f14bb7b8fe48a4e318ad07e910',
  'assets/images/emoji1.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/be9751678c0b3f9c6bf55f60de815d30',
  'assets/images/emoji2.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/7c0d14b79e134d528c5e0801699d6ccf',
  'assets/images/emoji3.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/d713e2de164764c2ab3db0ab4e40c577',
  'assets/images/emoji4.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/ac2163b98a973cb50bfb716cc4438f9a',
  'assets/images/emoji5.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/9cc0e2ff664bae3af766b9750331c3ad',
  'assets/images/emoji6.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/ce614cf0928157b3f7daa3cb8e7bd486',
  'components/ImageViewer.js': 'tutorial/02-image-picker/ImageViewer.js',
  'components/Button.js': 'tutorial/03-button-options/Button.js',
  'components/CircleButton.js': 'tutorial/03-button-options/CircleButton.js',
  'components/IconButton.js': 'tutorial/03-button-options/IconButton.js',
  'components/EmojiPicker.js': 'tutorial/04-modal/EmojiPicker.js',
  'components/EmojiList.js': 'tutorial/05-emoji-list/EmojiList.js',
  'components/EmojiSticker.js': 'tutorial/05-emoji-list/EmojiSticker.js',
}}>

{/* prettier-ignore */}
```jsx
// ...rest of the import statements
/* @info */import EmojiSticker from './components/EmojiSticker';/* @end */

export default function App() {
  // ...rest of the code remains same

  return (
    <View>
      <View style={styles.imageContainer}>
        <ImageViewer placeholderImageSource={PlaceholderImage} selectedImage={selectedImage} />
        /* @info */{pickedEmoji && <EmojiSticker imageSize={40} stickerSource={pickedEmoji} />}/* @end */
      </View>
      {/* ...rest of the code remains same */}
    </View>
  );
}
```

</SnackInline>

Let's take a look at our app on Android, iOS and the web:

<ContentSpotlight file="tutorial/select-emoji.mp4" />

</Step>

## Next step

We successfully created the emoji picker modal and implemented the logic to select an emoji and display it over the image.

<BoxLink
  title="Add gestures"
  Icon={BookOpen02Icon}
  description="In the next chapter, let's add user interactions with gestures to drag the emoji and scale the size by tapping it."
  href="/tutorial/gestures"
/>
